import time
import zlib
import sys
import sqlite3
import hashlib

import lonote.google_dmp.diff_match_patch as diff_match_patch
from PyQt4.QtCore import *
from PyQt4.QtGui import *

import lonote.html as html

def getSha224(string):
    '''解算string的sha224並回傳'''
    return hashlib.sha224(string.encode('utf-8')).hexdigest()

def savePage(asyncQueue, bookdb, id,
        needSaveHtml, needSaveModtime,
        scrollpos, vcsEnable,
        forceHistoryRecord, autosavePeriod, autosaveLenChanged,
        useLevenshtein):
    '''儲存頁面到bookdb中'''
    #print('Save Page Start!')
    #now = datetime.datetime.now()
    conn = sqlite3.connect(bookdb)

    #因為Qwebview產生的純文字和QTextDocument產生的不一樣，沒法用做比對用，所以一律改成QTextDocument模式
    doc = QTextDocument()
    doc.setHtml(needSaveHtml)
    needSavePlain = doc.toPlainText()

    #記錄當前頁面捲動位置
    if scrollpos != -1:
        conn.execute('update notes set scrollpos = :scrollpos where id = :id', {'scrollpos':scrollpos, 'id':id})

    #如果舊資料與新資料不相同才需要更新notes
    changed = bool(conn.execute('update notes set content = :htmlstr, contentplain = :plainstr, modtime = :modtime, len = :plainlen where id = :id and content <> :htmlstr', {'htmlstr':needSaveHtml, 'plainstr':needSavePlain, 'modtime':needSaveModtime, 'plainlen':len(needSavePlain), 'id':id}).rowcount)
    conn.commit()
    #delta = datetime.datetime.now() - now
    #print('Save Page Complete! time use:', delta)

    #將資料傳出
    if changed:
        asyncQueue.put(
                {
                    'type': 'pageNewSave',
                    'id': id
                    },
                True, 5)

    #儲存歷史紀錄
    name,modtime,content,plainLen = conn.execute('select name,modtime,content,len from notes where id = ?', (id,)).fetchone()
    conn.close()
    saveHistory(asyncQueue, bookdb, id,
        content, modtime, plainLen, name,
        vcsEnable, forceHistoryRecord, autosavePeriod, autosaveLenChanged,
        useLevenshtein)

def getHistory(bookdb, id, modtime):
    '''取得特定歷史版本
    bookdb = 筆記本的路徑
    不管底層格式為什麼，自動將其還原回標準的html格式'''
    conn = sqlite3.connect(bookdb)
    content,deltafrom = conn.execute('select content,deltafrom from history where id = ? and modtime = ?', (id,modtime)).fetchone()
    #content解壓縮
    content = zlib.decompress(content).decode('utf-8')
    #測試是否需要復原delta
    if deltafrom is None: #如果沒有delta訊息就不用做任何事
        pass
    else: #否則就將他恢復
        #載入delta來源
        deltaFromContent = conn.execute('select content from history where id = ? and modtime = ?', (id,deltafrom)).fetchone()[0]
        #deltaFromContent解壓縮
        deltaFromContent = zlib.decompress(deltaFromContent).decode('utf-8')
        dmp = diff_match_patch()
        content,is_OK = dmp.patch_apply(dmp.patch_fromText(content), deltaFromContent)

    conn.close()
    return content

def saveHistory(asyncQueue, bookdb, id,
        currentHtml, currentModtime, currentPlainLen, currentName,
        vcsEnable,
        forceHistoryRecord, autosavePeriod, autosaveLenChanged,
        useLevenshtein):
    '''替指定id的筆記儲存新版本

    asyncQueue = 回傳資料給主Process的窗口
    bookdb = 筆記資料庫的路徑
    id = 儲存處理的id
    currentHtml = 當前版本的筆記資料
    currentModtime = 當前版本的筆記是何時儲存的
    currentPlainLen = 當前版本筆記的字數有多少
    currentName = 當前版本筆記的名稱
    forceHistoryRecord = 看是否要強制儲存的真假值
    autosavePeriod = 自動儲存的時間周期（秒）
    autosaveLenChanged = 自動儲存所需要的字數變更（字元）
    useLevenshtein = 使用Levenshtein差異算法
    '''

    def isNeedToSaveHistory():
        '''檢查是否要儲存歷史'''
        #狀態測試：現在有沒有History，沒有則需要儲存
        if not conn.execute('select modtime from history where id = ?', (id,)).fetchone():
            return True

        #確定至少一筆History有存在
        #準備後續檢查需要的參數
        lastRecordModtime,lastRecordLen = conn.execute('select modtime,len from history where modtime = (select max(modtime) from history where id = ?)', (id,)).fetchone()
        timeChanged = time.mktime(time.localtime()) - float(lastRecordModtime)
        #決定字數的演算算法
        if useLevenshtein: #文字個別變化
            lastHtml = getHistory(bookdb, id, lastRecordModtime)
            doc = QTextDocument()
            doc.setHtml(lastHtml)
            lastPlain = doc.toPlainText()
            currentPlain = conn.execute('select contentplain from notes where id = ?', (id,)).fetchone()[0]
            diffs = dmp.diff_main(lastPlain, currentPlain)
            lenChanged = dmp.diff_levenshtein(diffs)
        else: #文字總數變化
            lenChanged = abs(currentPlainLen - lastRecordLen)

        #asyncQueue.put(
        #        {
        #            'type': 'newDiff',
        #            'id': id,
        #            'diffLen':lenChanged},
        #        False, 5)

        #檢查「強制更新」與「時間參數」與「變更字數」
        renew = False
        if forceHistoryRecord:
            renew = True
        #如果時間參數為0，表示不依據時間變更條件更新
        elif (autosavePeriod != 0) and timeChanged > autosavePeriod:
            renew = True
        #如果字數參數為0，表示不依據字數變更條件更新
        elif (autosaveLenChanged != 0) and lenChanged > autosaveLenChanged:
            renew = True

        if not renew: #如果居然沒有一項條件滿足則放棄
            return False

        #檢測history中有沒有id和sha224兩者同時相同的例子（這表示如果儲存了將會是一筆重複的記錄），有就放棄儲存。
        sha224 = getSha224(currentHtml)
        if conn.execute('select modtime from history where id = ? and sha224 = ?' ,(id ,sha224)).fetchone():
            return False

        #如果前面攔阻的檢查都通過，則確定儲存
        return True

    def saveHistoryBackend():
        '''選擇寫入模式並實際寫入'''
        #先準備好要存的資料
        #name,modtime,content,plainLen = self.conn.execute('select name,modtime,content,len from notes where id = ?', (id,)).fetchone()

        #先把基礎資料進行壓縮，並算出sha224
        contentCompress = zlib.compress(currentHtml.encode('utf-8'))
        sha224 = getSha224(currentHtml)

        #決定要不要使用差分編碼
        useDelta = False
        #原本沒有History舊記錄的話，當然沒法用上差分編碼
        if not conn.execute('select modtime from history where id = ?', (id,)).fetchone():
            useDelta = False
        else:
            #取回最新的History資料
            lastNonDeltaModtime = conn.execute('select modtime from history where id = ? and modtime = (select max(modtime) from history where id = ? and deltafrom is null)', (id,id)).fetchone()[0]
            lastNonDeltaContent = getHistory(bookdb, id, lastNonDeltaModtime)
            #嘗試用預備儲存的文字與lastNonDeltaContent產生patch,並轉成text格式
            deltaText = dmp.patch_toText(dmp.patch_make(lastNonDeltaContent, currentHtml))
            deltaTextCompress = zlib.compress(deltaText.encode('utf-8'))
            #如果deltaText比較省空間，用deltaText儲存，否則用標準版本進行儲存
            if sys.getsizeof(deltaTextCompress) < sys.getsizeof(contentCompress):
                useDelta = True

        #具體儲存
        if useDelta:
            #print('====== 使用Delta ======')
            conn.execute('insert into history(id,name,modtime,content,deltafrom,sha224,len) values(:id,:name,:modtime,:content,:deltatime,:sha224,:len)', {'id':id, 'name':currentName, 'modtime':currentModtime, 'content':deltaTextCompress, 'deltatime':lastNonDeltaModtime, 'sha224':sha224, 'len':currentPlainLen})
        else:
            #print('====== 使用原文 ======')
            conn.execute('insert into history(id,name,modtime,content,sha224,len) values(:id,:name,:modtime,:content,:sha224,:len)', {'id':id, 'name':currentName, 'modtime':currentModtime, 'content':contentCompress, 'sha224':sha224, 'len':currentPlainLen})

        #提交
        conn.commit()
        #將資料傳出
        asyncQueue.put(
                {
                    'type': 'historyUpdate',
                    'id': id,
                    'modtime': currentModtime,
                    'plainLen':currentPlainLen,
                    'name': currentName},
                True, 5)


    #沒開啟版本功能無論如何都不需要
    if not vcsEnable:
        return False

    conn = sqlite3.connect(bookdb)

    dmp = diff_match_patch() #差分編碼用

    if isNeedToSaveHistory():
        saveHistoryBackend()

    conn.close()

def genHeadingItems(asyncQueue, id, currentHtml):
    '''計算currentHtml中的Heading和順序'''
    #將資料傳出
    asyncQueue.put(
            {
                'type': 'headingItemsReady',
                'id': id,
                'items': html.getHeadingItems(currentHtml)}, #重點
            True, 5)

def saveListExpanded(bookdb, datas):
    '''儲存並產生新的ListView資料
    datas[id] == expanded'''
    conn = sqlite3.connect(bookdb)
    for id in datas:
        conn.execute('update notes set list_expanded = :le where id = :id and list_expanded <> :le', {'le': datas[id], 'id': id})
    conn.commit()
    conn.close()
